package com.only4play.portal.common.session;

import java.time.Instant;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author liyuncong
 * @version 1.0
 * @file StandardSession
 * @brief StandardSession
 * @details StandardSession
 * @date 2024-06-12
 *
 * Edit History
 * ----------------------------------------------------------------------------
 * DATE                     NAME               DESCRIPTION
 * 2024-06-12               liyuncong          Created
 */
public class StandardSession implements PortalSession, InternalSession {

    protected Map<String, Object> attributes = new ConcurrentHashMap<>();

    /**
     * The session identifier of this Session
     */
    protected String id = null;

    protected volatile boolean isValid = false;

    protected transient volatile boolean expiring = false;

    /**
     * The Manager with which this Session is associated
     */
    protected transient InternalSessionManager manager = null;

    // --------------------------------- InternalSession

    protected long creationTime = 0L;

    protected volatile long thisAccessedTime = this.creationTime;

    /**
     * The default maximum inactive interval for Sessions created by this Manager
     */
    protected int maxInactiveInterval = 30 * 60;

    /**
     * The facade associated with this session
     */
    protected transient StandardSessionFacade facade = null;

    /**
     * The access count for this session
     */
    protected transient AtomicInteger accessCount = null;

    public StandardSession(InternalSessionManager manager) {
        this.manager = manager;
        this.accessCount = new AtomicInteger();
    }

    @Override
    public PortalSession getSession() {
        if (this.facade == null) {
            this.facade = new StandardSessionFacade(this);
        }

        return this.facade;
    }

    protected boolean isValidInternal() {
        return this.isValid;
    }

    @Override
    public boolean isValid() {
        if (!this.isValid) {
            return false;
        }

        if (this.expiring) {
            return true;
        }

        if (this.accessCount.get() > 0) {
            return true;
        }

        if (this.maxInactiveInterval > 0) {
            long timeNow = Instant.now().toEpochMilli();
            int timeIdle;
            timeIdle = (int) ((timeNow - this.thisAccessedTime) / 1000L);
            if (timeIdle >= this.maxInactiveInterval) {
                expire();
            }
        }

        return this.isValid;
    }

    @Override
    public void setValid(boolean isValid) {
        this.isValid = isValid;
    }

    @Override
    public String getIdInternal() {
        return this.id;
    }

    @Override
    public void expire() {
        if (!this.isValid) {
            return;
        }

        synchronized (this) {
            if (this.expiring || !this.isValid) {
                return;
            }

            if (this.manager == null) {
                return;
            }

            // Mark this session as "being expired"
            this.expiring = true;
            this.accessCount.set(0);

            // Remove this session from our manager's active sessions
            this.manager.remove(this, true);

            setValid(false);
            this.expiring = false;

            String[] keys = keys();
            for (String key : keys) {
                removeAttributeInternal(key);
            }
        }
    }

    protected String[] keys() {
        return this.attributes.keySet().toArray(new String[0]);
    }

    @Override
    public void access() {
        this.thisAccessedTime = Instant.now().toEpochMilli();
        this.accessCount.decrementAndGet();
    }

    @Override
    public void endAccess() {
        this.thisAccessedTime = Instant.now().toEpochMilli();
        this.accessCount.decrementAndGet();
    }

    @Override
    public void setCreationTime(long time) {
        this.creationTime = time;
        this.thisAccessedTime = time;
    }

    @Override
    public void setMaxInactiveInterval(int interval) {
        this.maxInactiveInterval = interval;
    }

    @Override
    public void setId(String id) {
        if ((this.id != null) && (this.manager != null)) {
            this.manager.remove(this);
        }

        this.id = id;
        if (this.manager != null) {
            this.manager.add(this);
        }
    }

    @Override
    public Object getAttribute(String name) {
        if (!isValidInternal()) {
            throw new IllegalStateException("getAttribute: Session already invalidated");
        }

        if (name == null) {
            return null;
        }
        return this.attributes.get(name);
    }

    @Override
    public Enumeration<String> getAttributeNames() {
        if (!isValidInternal()) {
            throw new IllegalStateException("getAttributeNames: Session already invalidated");
        }

        Set<String> names = new HashSet<>(this.attributes.keySet());
        return Collections.enumeration(names);
    }

    @Override
    public void setAttribute(String name, Object value) {
        // Name cannot be null
        if (name == null) {
            throw new IllegalArgumentException("setAttribute: name parameter cannot be null");
        }

        // Null value is the same as removeAttribute()
        if (value == null) {
            removeAttribute(name);
            return;
        }

        // Validate our current state
        if (!isValidInternal()) {
            throw new IllegalStateException("setAttribute: Session [" + getIdInternal() + "] has already been invalidated");
        }

        this.attributes.put(name, value);
    }

    @Override
    public void removeAttribute(String name) {
        removeAttributeInternal(name);
    }

    protected void removeAttributeInternal(String name) {
        if (name == null) {
            return;
        }
        this.attributes.remove(name);
    }

    @Override
    public void invalidate() {
        if (!isValidInternal()) {
            throw new IllegalStateException("invalidate: Session already invalidated");
        }

        // Cause this session to expire
        expire();
    }
}
